#include "BaiduPan.h"
#include "BaiduToken.h"
#include <QEventLoop>
#include <QJsonArray>
#include <QUrl>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QCryptographicHash>
#include <QRandomGenerator>

BaiduPan::BaiduPan(QObject *parent)
    : QObject{parent}
{
    manager = new QNetworkAccessManager(this);
    client_id = BaiduToken::client_id;
    pan_name = BaiduToken::name;
}

/** 用户信息 */
QString BaiduPan::info()
{
    return http("https://pan.baidu.com/rest/2.0/xpan/nas?method=uinfo");
}
/** 获取网盘容量信息 */
QString BaiduPan::quota()
{
    return http("https://pan.baidu.com/api/quota?");
}
/** 文件列表 */
QString BaiduPan::list(QString path)
{
    return http("https://pan.baidu.com/rest/2.0/xpan/file?method=list&dir=" + path);
}
/** 文件列表所有 */
QString BaiduPan::listAll(QString path)
{
    return http("https://pan.baidu.com/rest/2.0/xpan/multimedia?method=listall&path=" + path + "&start=0&recursion=1");
}
/** 文件管理删除 */
QString BaiduPan::remove(QString path){
    QByteArray qba("async=1&filelist=");
    qba.append("[\"").append(path.toUtf8().toPercentEncoding()).append("\"]");
    return http("https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&opera=delete", qba);
}
/** 文件管理移动 */
QString BaiduPan::move(QString path, QString dest, QString newname){
    QByteArray qba("async=1&filelist=");
    qba.append("[{\"path\":\"").append(path.toUtf8().toPercentEncoding()).append("\",\"dest\":\"").append(dest.toUtf8().toPercentEncoding()).append("\",\"newname\":\"").append(newname.toUtf8().toPercentEncoding()).append("\"}]");
    return http("https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&opera=move", qba);
}
/** 文件管理复制 */
QString BaiduPan::copy(QString path, QString dest, QString newname){
    QByteArray qba("async=1&filelist=");
    qba.append("[{\"path\":\"").append(path.toUtf8().toPercentEncoding()).append("\",\"dest\":\"").append(dest.toUtf8().toPercentEncoding()).append("\",\"newname\":\"").append(newname.toUtf8().toPercentEncoding()).append("\"}]");
    return http("https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&opera=copy", qba);
}
/** 文件管理重命名 */
QString BaiduPan::rename(QString path, QString newname){
    QByteArray qba("async=1&filelist=");
    qba.append("[{\"path\":\"").append(path.toUtf8().toPercentEncoding()).append("\",\"newname\":\"").append(newname.toUtf8().toPercentEncoding()).append("\"}]");
    return http("https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&opera=rename", qba);
}
/** 本地文件列表所有 */
QString BaiduPan::localAll(QString path)
{
    QJsonArray jarr;

    QFileInfo fileinfo(path);
    if(fileinfo.isDir()){
        localFile(path, jarr);
    }else{
        QJsonObject jo;
        jo.insert("lpath", path);
        jo.insert("fileName", fileinfo.fileName());
        jo.insert("size", fileinfo.size());
        jo.insert("isDir", false);
        jarr.push_back(jo);
    }

    QJsonObject jobj;
    jobj.insert("errno", 0);
    jobj.insert("list", jarr);
    QJsonDocument jd(jobj);
    return QString(jd.toJson(QJsonDocument::Compact));
}
/** 本地文件列表 */
void BaiduPan::localFile(QString path, QJsonArray &ret)
{
    qDebug() << "localFile" << path;
    QDir dir(path);
    QFileInfoList list = dir.entryInfoList();
    for(int i=0;i<list.size();i++){
        if(list[i].fileName() == "." || list[i].fileName() == "..") continue;
        if(list[i].isDir()){
            localFile(path + "/" + list[i].fileName(), ret);
        }else{
            QJsonObject jo;
            jo.insert("lpath", path);
            jo.insert("fileName", list[i].fileName());
            jo.insert("size", list[i].size());
            jo.insert("isDir", false);
            ret.push_back(jo);
        }
    }
}
/** 比较盘跟本地文件列表用于下载 */
QString BaiduPan::cmpPanToLocal(QString lpath, QString spath)
{
    QString jstr = listAll(spath);
    QJsonDocument jdoc = QJsonDocument::fromJson(jstr.toUtf8());
    if(jdoc["errno"].toInt() > 0){
        qDebug() << "download error" << jstr;
        return jstr;
    }
    QJsonArray jarr;

    QJsonArray ja = jdoc["list"].toArray();
    QFile fl;
    for(auto qo : ja) {
        QJsonObject qob = qo.toObject();
        if(qob["isdir"].toBool() == true){ //是目录
            continue;
        }
        //文件处理
        QString lf = lpath + qob["path"].toString().replace(spath, "");
        fl.setFileName(lf);
        if(fl.exists() && fl.size() == qob["size"].toInt()){
            continue;
        }
        qob.insert("savepath", lf.replace(qob["server_filename"].toString(), ""));
        qob.insert("csize", 0);
        qob.insert("sname", qob["server_filename"].toString());
        jarr.push_back(qob);
    }

    QJsonObject jobj;
    jobj.insert("errno", 0);
    jobj.insert("list", jarr);
    QJsonDocument jd(jobj);
    return QString(jd.toJson(QJsonDocument::Compact));
}
/** 比较本地跟盘文件列表用于上传 */
QString BaiduPan::cmpLocalToPan(QString lpath, QString spath)
{
    qDebug() << "cmpLocalToPan" << lpath << spath;
    //获取盘文件
    QString jstr = listAll(spath);
    QJsonDocument jdoc = QJsonDocument::fromJson(jstr.toUtf8());
    if(jdoc["errno"].toInt() > 0){
        qDebug() << "download error" << jstr;
        return jstr;
    }
    //要上传的文件
    QJsonArray jarr;

    checkLocalPan(lpath, lpath.size(), spath, jdoc["list"].toArray(), jarr);

    QJsonObject jobj;
    jobj.insert("errno", 0);
    jobj.insert("list", jarr);
    QJsonDocument jd(jobj);
    return QString(jd.toJson(QJsonDocument::Compact));
}
/** 检查服务上是否存在本地的文件 */
void BaiduPan::checkLocalPan(QString lpath, qsizetype blen, QString spath, QJsonArray pfiles, QJsonArray &ret)
{
    QDir dir(lpath);

    QFileInfoList list = dir.entryInfoList();
    for(int i=0;i<list.size();i++){
        if(list[i].fileName() == "." || list[i].fileName() == "..") continue;
        if(list[i].isDir()){
            checkLocalPan(lpath + "/" + list[i].fileName(), blen, spath, pfiles, ret);
        }else{
            bool fd = false;
            QString lf = spath + lpath.mid(blen) + "/" + list[i].fileName();
            qDebug() << "lf" << lf;
            QJsonArray ja = pfiles; //不加这行代码有警告 clazy-range-loop-detach
            for(auto qo : ja) {
                QJsonObject qob = qo.toObject();
                if(qob["isdir"].toBool() == true){ //是目录
                    continue;
                }
                if(qob["path"].toString() == lf && list[i].size() == qob["size"].toInteger()){
                    fd = true;
                    break;
                }
            }
            if(!fd){
                QJsonObject jo;
                jo.insert("savepath", spath);
                jo.insert("fileName", list[i].fileName());
                jo.insert("csize", 0);
                jo.insert("size", list[i].size());
                jo.insert("isDir", false);
                ret.push_back(jo);
            }
        }
    }
}
/** 上传文件 上传有目录现在只能是apps目录下的申请接入的名称 我的名称是 我的盘 */
void BaiduPan::upload(QString lpath, QString spath)
{
    //读取文件计算md5
    QFile upf(lpath);
    if(!upf.open(QIODevice::ReadOnly)){
        emit uploadError("读取文件失败 " + lpath);
        return;
    }
    QString block_list = "[";
    QByteArray qba = upf.readAll();
    for(int i = 0;i < qba.size(); i+=4 * 1024 * 1024){
        QByteArray md5 = QCryptographicHash::hash(qba.mid(i, 4 * 1024 * 1024), QCryptographicHash::Md5).toHex();
        block_list += "\"" + md5 + "\"";
    }
    block_list += "]";
    qDebug() << block_list;
    //预上传文件
    QByteArray data("path=");
    data.append(spath.toUtf8().toPercentEncoding()).append("&size=").append(QByteArray::number(qba.size())).append("&isdir=0&autoinit=1&rtype=0&block_list=").append(block_list.toUtf8());
    QString pre = http("https://pan.baidu.com/rest/2.0/xpan/file?method=precreate", data);
    //qDebug() << "precreate" << pre;
    QJsonDocument jdoc = QJsonDocument::fromJson(pre.toUtf8());
    if(jdoc["errno"].toInt() > 0){
        emit uploadError("预上传出错了");
        qDebug() << "precreate" << pre;
        return;
    }
    if(!(jdoc["return_type"].toInt() == 2 || jdoc["block_list"].toArray().size() == 0)){ //未上传过文件了
        //分片上传
        int partseq = 0;
        for(int i = 0;i < qba.size(); i+=4 * 1024 * 1024){
            //qDebug() << "partseq" << i;
            QNetworkRequest request(QUrl("https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?method=upload&type=tmpfile&path=" + spath.toUtf8().toPercentEncoding() + "&uploadid=" + jdoc["uploadid"].toString() + "&partseq=" + QString::number(partseq) + "&access_token=" + BaiduToken::access_token));

            QString boundary = "----HONG" + QString::number(QRandomGenerator::global()->generate64());
            request.setHeader(QNetworkRequest::UserAgentHeader, "pan.baidu.com");
            request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary);
            QByteArray sqb = qba.mid(i, 4 * 1024 * 1024);
            QByteArray sub("--");
            sub.append(boundary.toUtf8()).append("\r\n")
                    .append("Content-Disposition: form-data; name=\"file\"; filename=\"upfile\"\r\n")
                    .append("Content-Type: application/octet-stream\r\n\r\n")
                    .append(sqb)
                    .append("\r\n--").append(boundary.toUtf8()).append("--\r\n");
            request.setHeader(QNetworkRequest::ContentLengthHeader, sub.size());
            QNetworkReply *rpl = manager->post(request, sub);

            QEventLoop eventLoop;
            connect(manager, SIGNAL(finished(QNetworkReply*)), &eventLoop, SLOT(quit()));
            eventLoop.exec();
            QByteArray upr = rpl->readAll();
            rpl->deleteLater();
            //qDebug() << "partseqend" << upr;
            QJsonDocument jup = QJsonDocument::fromJson(upr);
            if(jup["errno"].toInt() > 0){
                emit uploadError(QString(upr));
                return;
            }

            emit uploadChange(sqb.size());
            partseq++;
        }
    }
    //创建文件
    QByteArray cdata("path=");
    cdata.append(spath.toUtf8().toPercentEncoding()).append("&size=").append(QByteArray::number(qba.size())).append("&isdir=0&rtype=0&uploadid=").append(jdoc["uploadid"].toString().toUtf8()).append("&block_list=").append(block_list.toUtf8());
    QString crt = http("https://pan.baidu.com/rest/2.0/xpan/file?method=create", cdata);
    //qDebug() << "create" << crt;
    QJsonDocument jcrt = QJsonDocument::fromJson(crt.toUtf8());
    if(jcrt["error_code"].toInt() > 0){
        qDebug() << "create" << crt;
    }else{
        emit uploadFinish();
    }
}
/** 下载文件 */
void BaiduPan::download(QString lpath, QString spath)
{
    QString jstr = http("https://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas&fsids=" + spath + "&dlink=1");
    QJsonDocument jdoc = QJsonDocument::fromJson(jstr.toUtf8());
    if(jdoc["errno"].toInt() > 0){
        qDebug() << "download error" << jstr;
        return;
    }
    QString ldurl = jdoc["list"].toArray().at(0).toObject().value("dlink").toString();
    qDebug() << "ldurl" << ldurl;

    QNetworkRequest request((QUrl(ldurl + "&access_token=" + BaiduToken::access_token)));
    //必须要添加不然会报错
    request.setHeader(QNetworkRequest::UserAgentHeader, "pan.baidu.com");
    request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::UserVerifiedRedirectPolicy);
    QNetworkReply *rpl = manager->get(request);
    connect(rpl, &QNetworkReply::redirected, this, [=](QUrl url){
        qDebug() << "redirected " << url;
        if(url.url().indexOf("issuecdn") != -1){
            qDebug() << "文件有违规内容已被清理";
            emit loadError("文件有违规内容已被清理");
            return;
        }
        //下载文件
        saveFile.setFileName(lpath);
        if(!saveFile.open(QIODevice::ReadWrite)){
            QString dirstr = lpath.mid(0, lpath.lastIndexOf("/"));
            qDebug() << "dir" << dirstr;
            QDir dir(dirstr);
            dir.mkpath(dirstr);
            saveFile.open(QIODevice::ReadWrite);
        }

        QNetworkRequest req((QUrl(url.url() + "&access_token=" + BaiduToken::access_token)));
        //必须要添加不然大文件会报错
        req.setHeader(QNetworkRequest::UserAgentHeader, "pan.baidu.com");
        QNetworkReply *rpl2 = manager->get(req);
        //connect(rpl2, &QNetworkReply::redirected, this, [=](QUrl url){
        //    qDebug("redirected 2 %s", qPrintable(url.url()));
        //});

        connect(rpl2, &QNetworkReply::readyRead, this, [=](){
            QByteArray qba = rpl2->readAll();
            saveFile.write(qba);
            emit loadChange(qba.size());
        });
        connect(rpl2, &QNetworkReply::finished, this, [=](){
            //QByteArray qba = rpl2->readAll();
            qDebug() << "down finished";
            saveFile.close();
            rpl2->deleteLater();
            emit loadFinish();
        });
    });
    connect(rpl, &QNetworkReply::finished, this, [=](){
        qDebug() << "ldurl end" << rpl->readAll();
        rpl->deleteLater();
        emit loadError("出错了");
    });
}
/** 创建本地路径 */
void BaiduPan::createLocalPath(QString path)
{
    QDir cdir(path);
    if(!cdir.exists()){
        cdir.mkpath(path);
    }
    emit loadFinish();
}
/** http请求 */
QString BaiduPan::http(QString url, QByteArray data)
{
    qDebug() << "http" << url;
    QNetworkRequest request(QUrl(url + "&access_token=" + BaiduToken::access_token));
    //qDebug(request.url().url().toStdString().c_str());
    request.setHeader(QNetworkRequest::UserAgentHeader, "pan.baidu.com");
    if(data.isNull()){
        reply = manager->get(request);
    }else{
        qDebug() << "POST" << data.size();
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
        request.setHeader(QNetworkRequest::ContentLengthHeader, data.size());
        reply = manager->post(request, data);
    }
    QEventLoop eventLoop;
    connect(manager, SIGNAL(finished(QNetworkReply*)), &eventLoop, SLOT(quit()));
    eventLoop.exec();
    //return QJsonDocument::fromJson(reply->readAll());
    reply->deleteLater();
    return QString(reply->readAll());
}
